package com.hksj.wmp.service;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.weixin.WeixinRequestExecutor;
import com.foxinmy.weixin4j.http.weixin.WeixinResponse;
import com.foxinmy.weixin4j.model.WeixinAccount;
import com.foxinmy.weixin4j.token.TokenManager;
import com.foxinmy.weixin4j.wxa.WeixinAppFacade;
import com.foxinmy.weixin4j.wxa.model.Session;
import com.hksj.common.core.common.ClientType;
import com.hksj.common.core.exception.ApplicationException;
import com.hksj.common.core.exception.WxUnboundException;
import com.hksj.jetlinks.entity.User;
import com.hksj.jetlinks.model.LoginUser;
import com.hksj.jetlinks.service.JetUserService;
import com.hksj.jetlinks.vo.UserDetailVO;
import com.hksj.wmp.auth.WxUsernamePasswordAuthenticationToken;
import com.hksj.wmp.config.LoginProperties;
import com.hksj.wmp.dto.UserInfoDTO;
import com.hksj.wmp.dto.WxLoginDTO;
import com.hksj.wmp.dto.WxPhoneNumberDTO;
import com.hksj.wmp.entity.WxOpenId;
import com.hksj.wmp.manager.WxTokenManager;
import com.hksj.wmp.mapper.WxMenuMapper;
import com.hksj.wmp.vo.LoginInfoVO;
import com.hksj.wmp.vo.LoginUserInfoVO;
import com.hksj.wmp.vo.MenuVO;
import com.hksj.wmp.vo.WxUserPhoneNumberVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
public class WxLoginService {
    @Autowired
    private WxOpenIdService wxOpenIdService;
    @Autowired
    private JetUserService jetUserService;
    @Autowired
    private WxTokenManager wxTokenManager;
    @Autowired
    private WxMenuMapper wxMenuMapper;
    @Autowired
    private LoginProperties loginProperties;
    @Autowired
    private AuthenticationManager authenticationManager;

    @Value("${hk.wx.appId}")
    private String appId;

    @Value("${hk.wx.appSecret}")
    private String appSecret;

    @Autowired
    private WeixinRequestExecutor weixinRequestExecutor;
    @Autowired
    private TokenManager accessTokenManager;

    public static final String GET_USER_PHONE_NUMBER_URI = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s";

    public LoginInfoVO wxLogin(WxLoginDTO dto) {
        LoginInfoVO loginInfoVO = null;
        Session session = jscode2session(dto.getCode());
        WxOpenId existWxOpenId = wxOpenIdService.lambdaQuery()
                .eq(WxOpenId::getOpenId, session.getOpenId())
                .last("limit 1")
                .one();
        if (ObjectUtils.isEmpty(existWxOpenId)){
            if (ObjectUtils.isEmpty(dto.getUsername())){
                throw new WxUnboundException("当前微信未绑定系统账号");
            }else{
                loginInfoVO = doLogin(dto);

                //清除其他的登录信息
                wxOpenIdService
                        .lambdaUpdate()
                        .eq(WxOpenId::getUserId,loginInfoVO.getUserInfo().getId())
                        .remove();

                WxOpenId wxOpenId = new WxOpenId();
                wxOpenId.setUserId(loginInfoVO.getUserInfo().getId());
                wxOpenId.setUsername(loginInfoVO.getUserInfo().getUsername());
                wxOpenId.setOpenId(session.getOpenId());
                wxOpenId.setUnionId(session.getUnionId());
                wxOpenId.setSessionKey(session.getSessionKey());
                wxOpenIdService.save(wxOpenId);
            }
        }else{
            User user = jetUserService.getById(existWxOpenId.getUserId());
            if (ObjectUtils.isEmpty(user)){
                wxOpenIdService.removeById(existWxOpenId.getId());
                throw new WxUnboundException("当前微信未绑定系统账号");
            }else{
                existWxOpenId.setSessionKey(session.getSessionKey());
                existWxOpenId.setUnionId(session.getUnionId());
                wxOpenIdService.updateById(existWxOpenId);
                loginInfoVO = getLoginInfoVO(user);
            }
        }
        return loginInfoVO;
    }
    private LoginInfoVO doLogin(WxLoginDTO dto){
        Authentication authenticate = authenticationManager.authenticate(new WxUsernamePasswordAuthenticationToken(dto.getUsername(), dto.getPassword()));
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        return wrapperLoginInfo(loginUser);
    }

    public LoginInfoVO getLoginInfoVO(User user){
        //认证通过 拼装LoginInfoVO
        List<MenuVO> menus = listMenuByUserId(user.getId());
        List<String> menuCodeList = menus.stream().map(menu -> menu.getCode()).collect(Collectors.toList());
        user.setPassword(null);
        user.setSalt(null);
        LoginUser loginUser = new LoginUser(user, menuCodeList, null, ClientType.MINI_PROGRAM.value());
        LoginInfoVO loginInfoVO = wrapperLoginInfo(loginUser);
        return loginInfoVO;
    }

    public void wxLogout() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        wxOpenIdService
                .lambdaUpdate()
                .eq(WxOpenId::getUsername, loginUser.getUser().getUsername())
                .remove();
        wxTokenManager.logout(loginUser);
    }

    /**
     * 到微信服务器换取微信用户身份id
     * @param code
     * @return
     */
    public Session jscode2session(String code){
        WeixinAccount wxAccount = new WeixinAccount(appId, appSecret);
        WeixinAppFacade wxa = new WeixinAppFacade(wxAccount);
        Session session = null;
        try {
            session = wxa.getLoginApi().jscode2session(code);
        } catch (WeixinException e) {
            throw new ApplicationException("登录码已失效");
        }
        return session;
    }

    public LoginInfoVO wrapperLoginInfo(LoginUser loginUser){
        //把token响应给前端
        LoginInfoVO loginInfoVo = new LoginInfoVO();

        //挤掉其他登录状态
        wxTokenManager.kick(loginUser.getUser().getId(),ClientType.MINI_PROGRAM.value());

        //token
        String jwt = wxTokenManager.createToken(loginUser, loginProperties.getExpire());
        loginInfoVo.setToken(jwt);

        //用户信息
        User user = loginUser.getUser();
        LoginUserInfoVO loginUserInfo = getLoginInfo(user.getId());
        loginInfoVo.setUserInfo(loginUserInfo);

        //角色信息
        loginInfoVo.setRoles(loginUser.getRoles());

        //菜单权限
        List<MenuVO> menus = listMenuByUserId(user.getId());
        menus = toMenuTree(menus);
        loginInfoVo.setMenus(menus);

        return loginInfoVo;
    }

    public LoginUserInfoVO getLoginInfo(String userId){
        UserDetailVO userDetailVO = jetUserService.getUserDetail(userId);
        LoginUserInfoVO loginUserInfo = new LoginUserInfoVO();
        loginUserInfo.setId(userDetailVO.getId());
        loginUserInfo.setUsername(userDetailVO.getUsername());
        loginUserInfo.setNickName(userDetailVO.getName());
        loginUserInfo.setEmail(userDetailVO.getEmail());
        loginUserInfo.setPhoneNumber(userDetailVO.getTelephone());
        loginUserInfo.setAvatar(userDetailVO.getAvatar());
        loginUserInfo.setStatus(userDetailVO.getStatus());
        return loginUserInfo;
    }

    /**
     * 根据菜单列表 构建树型结构
     * @param menus 菜单列表
     * @return
     */
    public List<MenuVO> toMenuTree(List<MenuVO> menus){
        //模拟根节点
        MenuVO root = new MenuVO();
        root = toMenuTree(root,menus);
        return root.getChildList();
    }

    /**
     * 根据菜单列表 构建树型结构
     * @param root 根节点
     * @param menus 菜单列表
     * @return
     */
    private MenuVO toMenuTree(MenuVO root, List<MenuVO> menus){
        //初始化根菜单的子菜单列表
        if (ObjectUtils.isEmpty(root.getChildList())){
            root.setChildList(new ArrayList<>());
        }
        //查找子菜单
        for (MenuVO menu : menus) {
            if (ObjectUtils.isEmpty(root.getId())){
                if (ObjectUtils.isEmpty(menu.getParentId())){
                    MenuVO treeNode = toMenuTree(menu, menus);
                    root.getChildList().add(treeNode);
                }
            }else{
                if (Objects.equals(menu.getParentId(),root.getId())){
                    MenuVO treeNode = toMenuTree(menu, menus);
                    root.getChildList().add(treeNode);
                }
            }
        }
        return root;
    }

    public List<MenuVO> listMenuByUserId(String userId){
        return wxMenuMapper.listMenuByUserId(userId);
    }


    public LoginUserInfoVO updatePhoneNumber(UserInfoDTO dto) {
        jetUserService.updatePhoneNumber(dto.getUserId(),dto.getPhoneNumber());
        return getLoginInfo(dto.getUserId());
    }

    public WxUserPhoneNumberVO getWxPhoneNumber(WxPhoneNumberDTO dto) {
        WxUserPhoneNumberVO res = null;
        try {
            String accessToken = accessTokenManager.getAccessToken();
            JSONObject body = new JSONObject();
            body.put("code", dto.getCode());
            WeixinResponse response = weixinRequestExecutor.post(String.format(GET_USER_PHONE_NUMBER_URI, accessToken), body.toJSONString());
            res = response.getAsObject(new TypeReference<WxUserPhoneNumberVO>() {});
        }catch (Exception e){
            throw new ApplicationException(e.getMessage());
        }
        return res;
    }
}
